package il.ac.tau.cvws.scrabblesidekick.server.app.manager;

import il.ac.tau.cvws.scrabblesidekick.server.db.DictionaryAlphabet;
import il.ac.tau.cvws.scrabblesidekick.server.engine.dictionary.exception.DictionaryException;
import il.ac.tau.cvws.scrabblesidekick.server.engine.recoginition.TileImageMap;
import il.ac.tau.cvws.scrabblesidekick.server.engine.recoginition.calibration.common.CalibrationCompositeData;
import il.ac.tau.cvws.scrabblesidekick.server.engine.recoginition.calibration.stablecalibrate.CalibrationRecognizer;
import il.ac.tau.cvws.scrabblesidekick.server.engine.recoginition.tilelayout.TileLayoutRecognizer;
import il.ac.tau.cvws.scrabblesidekick.server.engine.recoginition.tilelayout.exception.LayoutRecognizerException;
import il.ac.tau.cvws.scrabblesidekick.server.engine.scrabble.ScrabbleSolver;
import il.ac.tau.cvws.scrabblesidekick.server.engine.storage.StorageKeyGenerator;
import il.ac.tau.cvws.scrabblesidekick.server.engine.storage.TileImageStorage;
import il.ac.tau.cvws.scrabblesidekick.server.util.exception.ScrabbleSidekickException;
import il.ac.tau.cvws.scrabblesidekick.server.util.file.ConversionUtil;
import il.ac.tau.cvws.scrabblesidekick.shared.datatype.ImageData;
import il.ac.tau.cvws.scrabblesidekick.shared.datatype.StorageDataKey;
import il.ac.tau.cvws.scrabblesidekick.shared.datatype.WordPlacement;
import il.ac.tau.cvws.scrabblesidekick.shared.datatype.game.Alphabet;
import il.ac.tau.cvws.scrabblesidekick.shared.datatype.game.Board;
import il.ac.tau.cvws.scrabblesidekick.shared.datatype.game.GameData;
import il.ac.tau.cvws.scrabblesidekick.shared.datatype.game.Rack;
import il.ac.tau.cvws.scrabblesidekick.shared.datatype.game.TileLayout;

import java.sql.SQLException;

import com.googlecode.javacv.cpp.opencv_core.IplImage;

/* Manages the game. This is the interface to the game's server-side engine.
 * It exposes device calibration and game solving functionalities.
 */
public class GameManager
{
	private static GameManager instance;
	
	private RunningMode mode;
	private ScrabbleSolver solver;
	private TileImageStorage storage;
	
	private GameManager()
	{
		mode 	= RunningMode.DEBUG;
		solver  = new ScrabbleSolver();
		storage = new TileImageStorage();
		
		StorageKeyGenerator.init();
	}
	
	/* Attempts to calibrate the device using the given image taken by the device's camera,
	 * and the given screen dimensions.
	 */
	public StorageDataKey calibrateDevice(ImageData boardImage, int screenWidth, int screenHeight)
	{
		IplImage layoutImage = IplImage.createFrom(ConversionUtil.convertStringToImage(boardImage.getImage()));
		
		CalibrationRecognizer calibrationRecognizer = new CalibrationRecognizer(screenWidth, screenHeight);
		CalibrationCompositeData resultData = calibrationRecognizer.recognize(layoutImage);
		
		if (resultData == null)
		{
			return null;
		}
		
		/* If calibration was successful, store the tiles and return the associated storage key.
		 */
		return storage.setTileImageMap(resultData.getTileImageMap());
	}

	/* Attempts to solve the game, given an image taken by the device's camera, the rack entered 
	 * by the user, the device's screen dimensions, the dictionary which is being used, and
	 * the storage key of the calibration data.
	 */
	public WordPlacement solveGame(ImageData boardImage, Rack rack,
			int screenWidth, int screenHeight, StorageDataKey storageKey, int dictionaryId) throws ScrabbleSidekickException
	{
		TileImageMap map = storage.getTileImageMap(storageKey.getKey());
		
		IplImage layoutImage = IplImage.createFrom(ConversionUtil.convertStringToImage(boardImage.getImage()));
		
		TileLayoutRecognizer layoutRecognizer = new TileLayoutRecognizer(map, screenWidth, screenHeight);
		TileLayout layout = layoutRecognizer.recognize(layoutImage);
		
		if (layout == null)
		{
			throw new LayoutRecognizerException(LayoutRecognizerException.FAILED_TO_RECOGNIZE_LAYOUT);
		}
		
		/* If the layout was recognized by the algorithm, create the solving request, and populate it:
		 * Board, rack, alphabet and dictionary.
		 */
		Board board = new Board(layout);
		System.out.println(layout);

		Alphabet alphabet = null;
		Rack generalRack = null;

		try 
		{
			alphabet   = DictionaryAlphabet.getAlphabet(dictionaryId);
			generalRack = DictionaryAlphabet.generalizeRack(rack, dictionaryId);
		}
		catch (SQLException e) 
		{
			throw new DictionaryException(DictionaryException.INVALID_DICTIONARY, e);
		}
		
		GameData gameData = new GameData(generalRack, board, alphabet, dictionaryId);

		/* Try to retrieve a possible solution given the board configuration.
		 */
		return solver.solveGame(gameData);
	}
	
	public static GameManager instance()
	{
		if (instance == null)
		{
			instance = new GameManager();
		}
		
		return instance;
	}
	
	public boolean debugMode()
	{
		return (mode == RunningMode.DEBUG);
	}
}
